#region File Header
//----------------------------------------------------------------
// Copyright (C) 2002 Stefan Kyntchev
// All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY 
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR 
// FITNESS FOR A PARTICULAR PURPOSE.
//----------------------------------------------------------------
#endregion File Header

using System;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
using System.IO;
using System.Collections;
using System.Diagnostics;

namespace AirWeb.Security
{
	/// <summary>
	/// One and Only one SecurityManager. Provides a set of methods to Authenticate users and Authorize
	/// the usage of resources
	/// </summary>
	/// 

	[Serializable]
	public sealed class SecurityMgr
	{
		/// <summary>
		/// Singleton - can't create new instances
		/// </summary>
		private SecurityMgr() {}

		/// <summary>
		/// Instance member is the only way to access the functionality of the security manager
		/// </summary>
		static SecurityMgr m_Instance = null;

		/// <summary>
		/// Retrieves an Instance to the security manager
		/// </summary>
		public static SecurityMgr Instance
		{
			get
			{
				if (SecurityMgr.m_Instance == null)
				{
					lock(typeof(SecurityMgr))
					{
						SecurityMgr.m_Instance = new SecurityMgr();
					}
				}

				return SecurityMgr.m_Instance;
			}
		}

		/// <summary>
		/// List of users
		/// </summary>
		ArrayList m_Users = new ArrayList();

		/// <summary>
		/// List of roles
		/// </summary>
		ArrayList m_Roles = new ArrayList();

		/// <summary>
		/// Path where the contents of the security manager will be serialized
		/// </summary>
		/// 
		static string m_SerializationPath = null;

		/// <summary>
		/// A file used to serilize/deserialize the security manager
		/// </summary>
		public string SerializationPath
		{
			get
			{
				return m_SerializationPath;
			}
			set
			{
				m_SerializationPath = value;
			}
		}

		/// <summary>
		/// Stores the security manager to a file
		/// </summary>
		public void Serialize()
		{
			lock(this)
			{
				FileStream s = new FileStream(m_SerializationPath,System.IO.FileMode.Create);
				SoapFormatter t = new SoapFormatter();
				t.Serialize(s,this);
				s.Close();
			}
		}

		/// <summary>
		/// Restores the security manager from a file
		/// </summary>
		public void Deserialize()
		{
			lock(this)
			{
				FileStream s = new FileStream(m_SerializationPath,System.IO.FileMode.Open);
				SoapFormatter t = new SoapFormatter();
				object o = t.Deserialize(s);
				SecurityMgr.m_Instance = (SecurityMgr)(o);
				s.Close();
			}
		}

		/// <summary>
		/// List of all available roles
		/// </summary>
		public ArrayList Roles
		{
			get
			{
				return m_Roles;
			}
		}

		/// <summary>
		/// List of all available Users
		/// </summary>
		public ArrayList Users
		{
			get
			{
				return m_Users;
			}
		}

		/// <summary>
		/// Retrieves a user given user credentials
		/// </summary>
		/// <param name="i_username">UserName</param>
		/// <param name="i_password">Password</param>
		/// <returns></returns>
		public User ValidateUser(string i_username, string i_password)
		{
			User u = GetUserByName(i_username);
			if(u != null && u.ValidatePassword(i_password))
				return u;
					
			return null;
		}

		/// <summary>
		/// Helper Function. Request a specified permission to access a particular resource
		/// </summary>
		/// <param name="i_user">the user</param>
		/// <param name="i_res">the resource</param>
		/// <param name="i_AccessLevel">Access level for the resource</param>
		/// <returns>true or false basd if the permission can be granted</returns>
		public bool RequestPermission(User i_user, IResource i_res, Right.AccessLevel i_AccessLevel)
		{
			Right r = new Right(i_res,i_AccessLevel);
			return i_user.UserRole.HasAtLeastRight(r);
		}

		/// <summary>
		/// Helper function. Requests a read permission to a particular resource
		/// </summary>
		/// <param name="i_user">the user</param>
		/// <param name="i_res">the resource</param>
		/// <returns>true or false basd if the permission can be granted</returns>
		public bool CanRead(User i_user, IResource i_res)
		{
			return RequestPermission(i_user,i_res, Right.AccessLevel.Read);
		}

		/// <summary>
		/// Helper Function. Request a specified permission to access a particular resource
		/// </summary>
		/// <param name="i_role">the Role</param>
		/// <param name="i_res">the resource</param>
		/// <param name="i_AccessLevel">Access level for the resource</param>
		/// <returns>true or false basd if the permission can be granted</returns>
		public bool RequestPermission(Role i_role, IResource i_res, Right.AccessLevel i_AccessLevel)
		{
			Right r = new Right(i_res,i_AccessLevel);
			return i_role.HasAtLeastRight(r);
		}

		/// <summary>
		/// Returns a role given role name
		/// </summary>
		/// <param name="i_role">the name of the role</param>
		/// <returns>Role object or null</returns>
		public Role GetRoleByName(string i_role)
		{
			foreach( Role r in Roles)
			{
				if(r.Name == i_role)
					return r;
			}
			return null;
		}

		/// <summary>
		/// Gets the user based on its login name
		/// </summary>
		/// <param name="i_username">login name of the user</param>
		/// <returns>User object or null</returns>
		public User GetUserByName(string i_username)
		{
			//make sure we are comparing the same cases
			i_username = i_username.ToLower();

			foreach( User u in Users)
			{
				if(u.Name == i_username)
					return u;
			}

			return null;
		}

		/// <summary>
		/// Gets the first user with the supplied email address
		/// </summary>
		/// <param name="i_useremail">user's e-mail</param>
		/// <returns>User object or null</returns>
		public User GetUserByEmail(string i_useremail)
		{
			//make sure we are comparing the same cases
			i_useremail = i_useremail.ToLower();

			foreach(User u in Users)
			{
				if(u.Email == i_useremail)
					return u;
			}

			return null;
		}

		/// <summary>
		/// Helper function. Sets the User with the supplied name to the supplied role
		/// </summary>
		/// <param name="i_user">the login name of the user</param>
		/// <param name="i_role">the role name</param>
		public void SetUserRole(string i_user,string i_role)
		{
			lock(this)
			{
				Role r = GetRoleByName(i_role);
				if(r == null)
					throw new ArgumentException("Invalid role supplied");

				User u = GetUserByName(i_user);
				if(u == null)
					throw new ArgumentException("Invalid User");

				u.UserRole = r;	
			}
		}

		/// <summary>
		/// Helper function. Creates a new user and adds it to the list of users
		/// </summary>
		/// <param name="i_username">Login name of the user</param>
		/// <param name="i_password">user password</param>
		/// <param name="i_email">user's e-mail address</param>
		/// <param name="i_role">name of the role for this user</param>
		/// <param name="i_extraData">optionally extra data for the user</param>
		/// <returns></returns>
		public User AddUser(string i_username, string i_password, string i_email, string i_role, object i_extraData)
		{
			lock(this)
			{
				if(i_username == "" || i_username.Length > 50)
					throw new ArgumentOutOfRangeException("Invalid user name supplied","Invalid user name supplied");

				if(i_password == "" || i_password.Length > 50)
					throw new ArgumentOutOfRangeException("Invalid password supplied","Invalid password supplied");

				if(i_role == "" || i_role.Length > 50)
					throw new ArgumentOutOfRangeException("Invalid user Role supplied","Invalid user Role supplied");

				// Check if a user with the same name already exists
				User old = GetUserByName(i_username);
				User old1 = GetUserByEmail(i_email);

				if(old != null)
					throw new ArgumentException("User " + i_username + " already exists");

				if(old1 != null)
					throw new ArgumentException("User with e-mail " + i_email + " already exists");

				Role r = GetRoleByName(i_role);
				if(r == null)
					throw new ArgumentException("Supplied role does not exist");

				User u = new User(i_username, i_password, i_email, r, i_extraData);
				Users.Add(u);
				return u;
			}
		}

		/// <summary>
		/// Adds a Role to the system
		/// </summary>
		/// <param name="i_name">Name of the role</param>
		/// <param name="i_description">User defined description of the role</param>
		/// <returns></returns>
		public Role AddRole(string i_name,string i_description)
		{
			lock(this)
			{
				if(i_name == "" || i_name.Length > 50)
					throw new ArgumentException("You must provide a name for the new Role","i_name");
				
				Role r = GetRoleByName(i_name);
				if(r != null)
					throw new ArgumentException("Add Role failed! Role with this name already exists",i_name);
			
				Role nr = new Role(i_name, i_description);
				Roles.Add(nr);
				return nr;
			}
		}

		/// <summary>
		/// Removes a Role from the system
		/// </summary>
		/// <param name="i_name">the path to the right</param>
		public void RemoveRole(string i_name)
		{
			lock(this)
			{
				if(i_name == "" || i_name.Length > 50)
					throw new ArgumentException("Invalid Role Specified");

				Role r = GetRoleByName(i_name);

				if(r == null)
					throw new ArgumentOutOfRangeException("Invalid role name supplied","Invalid role name supplied");

				Roles.Remove(r);
			}
		}

		/// <summary>
		/// Empties the Roles container
		/// </summary>
		public void RemoveAllRoles()
		{
			lock(this)
			{
				Roles.Clear();
			}
		}

		/// <summary>
		/// Removes a User from the system
		/// </summary>
		/// <param name="i_name">the name of the user</param>
		public void RemoveUser(string i_name)
		{
			lock(this)
			{
				if(i_name == "" || i_name.Length > 50)
					throw new ArgumentOutOfRangeException("Invalid user name supplied","Invalid user name supplied");

				User u = GetUserByName(i_name);
				if(u != null) 
				{
					Users.Remove(u);
				}
				else
					throw new ArgumentOutOfRangeException("Invalid user name supplied","Invalid user name supplied");
			}
		}

		/// <summary>
		/// Empties the Roles container
		/// </summary>
		public void RemoveAllUsers()
		{
			lock(this)
			{
				Users.Clear();
			}
		}
	}
}
